#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>

#include "infra_tiny_block.h"
#include "infra_u_tiny_block_priv.h"

infra_tb_handle infra_tb_create(uint32_t num, uint32_t size, bool is_lock, const char *token)
{
    int32_t node_size = 0, total_node_size = 0;
    int32_t idx = 0;
    infra_tb_ctx_t *ptb_ctx = NULL;
    void *pbase_vir_addr = NULL;
    infra_tb_node_t *pnode = NULL;

    if (0 == num || size == 0 || NULL == token) {
        infra_err("num:%d, size:%d, token:%s invalid\n", num, size, token);
        return NULL;
    }

    ptb_ctx = (infra_tb_ctx_t *)malloc(sizeof(infra_tb_ctx_t));
    if (NULL == ptb_ctx) {
        infra_err("[%8s] malloc tb ctx failed, %s\n",token, strerror(errno));
        return NULL;
    } else {
        memset(ptb_ctx, 0, sizeof(infra_tb_ctx_t));
    }

    node_size = sizeof(infra_tb_node_t);
    total_node_size = num * (node_size + size);
    pbase_vir_addr = malloc(total_node_size);
    if (NULL == pbase_vir_addr) {
        infra_err("[%8s] malloc tb node failed, %s\n", ptb_ctx->_token, strerror(errno));
        goto FAIL0;
    }
    INFRA_INIT_LIST_HEAD(&ptb_ctx->_total_list);
    INFRA_INIT_LIST_HEAD(&ptb_ctx->_free_list);

    pnode = (infra_tb_node_t *)pbase_vir_addr;
    for (idx = 0; idx < num; idx++) {
        INFRA_INIT_LIST_HEAD(&pnode->list);
        INFRA_INIT_LIST_HEAD(&pnode->free_list);
        infra_dl_list_add_tail(&pnode->list, &ptb_ctx->_total_list);
        infra_dl_list_add_tail(&pnode->free_list, &ptb_ctx->_free_list);
        pnode->magic = 0x5c5c;
        pnode->data = (void *)((void *)pnode + node_size);
        pnode = (infra_tb_node_t *)((void *)pnode + node_size + size);
    }

    strncpy(ptb_ctx->_token, token, INFRA_TOKEN_MAX_SIZE);
    ptb_ctx->_total_num = num;
    ptb_ctx->_free_num = num;

    ptb_ctx->_vir_addr = pbase_vir_addr;
    /* FIXME: phy_addr unsupport */
    ptb_ctx->_phy_addr = 0;

    ptb_ctx->_is_lock = is_lock;
    if (is_lock) {
        pthread_spin_init(&ptb_ctx->_lock, PTHREAD_PROCESS_PRIVATE);
    }

    return (infra_tb_handle)ptb_ctx;

FAIL0:
    if (NULL != ptb_ctx) {
        free(ptb_ctx);
        ptb_ctx = NULL;
    }

    return NULL;
}

int32_t infra_tb_destroy(infra_tb_handle handle)
{
    infra_tb_ctx_t *ptb_ctx = NULL;

    INFRA_PTR_CHECK(handle);

    ptb_ctx = (infra_tb_ctx_t *)handle;

    if (ptb_ctx->_total_num != ptb_ctx->_free_num) {
        infra_warn("some tiny block node need to be give back, free/total:%d/%d\n", ptb_ctx->_free_num,
                   ptb_ctx->_total_num);
    }

    free(ptb_ctx->_vir_addr);
    ptb_ctx->_vir_addr = NULL;

    if (ptb_ctx->_is_lock) {
        pthread_spin_destroy(&ptb_ctx->_lock);
    }
    ptb_ctx->_is_lock = false;

    ptb_ctx->_total_num = 0;
    ptb_ctx->_free_num = 0;

    ptb_ctx->_vir_addr = NULL;
    ptb_ctx->_phy_addr = 0;

    free(ptb_ctx);
    ptb_ctx = NULL;

    return 0;
}

void *infra_tb_get(infra_tb_handle handle)
{
    infra_tb_ctx_t *ptb_ctx = NULL;
    infra_tb_node_t *pnode = NULL;

    if (NULL == handle) {
        infra_err("%s is NULL\n", TO_STRING(handle));
        return NULL;
    }

    ptb_ctx = (infra_tb_ctx_t *)handle;

    if (ptb_ctx->_is_lock) {
        pthread_spin_lock(&ptb_ctx->_lock);
    }

    if (infra_dl_list_empty(&ptb_ctx->_free_list)) {
        infra_err("[%8s] tiny blk is empty\n", ptb_ctx->_token);
        goto EXIT;
    }

    pnode = infra_dl_list_first_entry(&ptb_ctx->_free_list, infra_tb_node_t,
                                      free_list);

    if (NULL != pnode) {
        infra_dl_list_del_init(&pnode->free_list);
        ptb_ctx->_free_num--;
    }

EXIT:
    if (ptb_ctx->_is_lock) {
        pthread_spin_unlock(&ptb_ctx->_lock);
    }

    return (pnode == NULL) ? NULL : pnode->data;
}

int32_t infra_tb_giveback(infra_tb_handle handle, void *pblk)
{
    infra_tb_ctx_t *ptb_ctx = NULL;
    infra_tb_node_t *pnode = NULL;

    INFRA_PTR_CHECK(handle);
    INFRA_PTR_CHECK(pblk);

    ptb_ctx = (infra_tb_ctx_t *)handle;

    if (ptb_ctx->_is_lock) {
        pthread_spin_lock(&ptb_ctx->_lock);
    }

    pnode = (infra_tb_node_t *)(pblk - sizeof(infra_tb_node_t));
    if (pnode->magic != 0x5c5c) {
        infra_err("[%8s] blk is invalid\n", ptb_ctx->_token);
        return -1;
    }

    infra_dl_list_add_tail(&pnode->free_list, &ptb_ctx->_free_list);
    ptb_ctx->_free_num++;

    if (ptb_ctx->_is_lock) {
        pthread_spin_unlock(&ptb_ctx->_lock);
    }

    return 0;
}